package my.mbean;

import my.mbean.support.GenericBeanVOBuilder;
import my.mbean.support.GenericPropertyVOBuilder;
import my.mbean.support.PropertyInfo;
import my.mbean.support.ValueWrapper;
import my.mbean.util.Utils;
import my.mbean.web.MBeanController;
import my.mbean.web.RequestDispatcher;
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.EventListener;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

@Configuration
//@EnableWebMvc
public class MBeanAutoConfiguration {

    @Resource
    private ApplicationContext context;

    @Bean
    @Lazy
    MBeanService beansService() {
        return new MBeanService();
    }

    @Bean
    @Lazy
    MBeanController mBeanController() {
        return new MBeanController();
    }

    @Bean
    @Lazy
    RequestDispatcher requestDispatcher() {
        return new RequestDispatcher();
    }


//    @EventListener(ContextRefreshedEvent.class)
//    public void registerMbeanServlet(ContextRefreshedEvent pEvent) {
//        ServletContext servletContext = ((WebApplicationContext) pEvent.getApplicationContext()).getServletContext();
//        MBeanServlet mbeanServlet = new MBeanServlet();
//        MBeanConfiguration mBeanConfiguration = mBeanConfiguration();
//        ServletRegistration.Dynamic registration = servletContext.addServlet(MBEAN_SERVLET_NAME, mbeanServlet);
//        registration.addMapping(mBeanConfiguration.getMbeanUrlPrefix() + "/*");
//    }
//    @Bean
//    WebApplicationInitializer mbeanRegistrar() {
//        return new WebApplicationInitializer() {
//            @Override
//            public void onStartup(ServletContext pServletContext) throws ServletException {
//                MBeanServlet mbeanServlet = new MBeanServlet();
//                ServletRegistration.Dynamic registration = pServletContext.addServlet(MBEAN_SERVLET_NAME, mbeanServlet);
//                MBeanConfiguration mBeanConfiguration = mBeanConfiguration();
//                registration.addMapping(mBeanConfiguration.getMbeanUrlPrefix() + "/*");
//            }
//        };
//    }
//    public class MBeanServletRegistrar implements WebApplicationInitializer {
//        @Override
//        public void onStartup(ServletContext pServletContext) throws ServletException {
//            MBeanServlet mbeanServlet = new MBeanServlet();
//            ServletRegistration.Dynamic registration = pServletContext.addServlet(MBEAN_SERVLET_NAME, mbeanServlet);
//            MBeanConfiguration mBeanConfiguration = mBeanConfiguration();
//            registration.addMapping(mBeanConfiguration.getMbeanUrlPrefix() + "/*");
//        }
//    }
//    @WebListener
//    class MbeanServletRegistion implements ServletContextListener {
//
//        @Override
//        public void contextInitialized(ServletContextEvent sce) {
//            MBeanServlet mbeanServlet = new MBeanServlet();
//            ServletRegistration.Dynamic registration = sce.getServletContext().addServlet(MBEAN_SERVLET_NAME, mbeanServlet);
//            MBeanConfiguration mBeanConfiguration = mBeanConfiguration();
//            registration.addMapping(mBeanConfiguration.getMbeanUrlPrefix() + "/*");
//        }
//
//        @Override
//        public void contextDestroyed(ServletContextEvent sce) {
//
//        }
//    }

//    @Bean
//    MBeanConfiguration mBeanConfiguration() {
//        return new MBeanConfiguration();
//    }



//    @ConditionalOnClass(ServletRegistrationBean.class)
//    class MBeanServletRegistrar {
//        @Bean
//        public ServletRegistrationBean mbeanServletRegistrationBean() {
//            ServletRegistrationBean registrationBean = new ServletRegistrationBean();
//            MBeanConfiguration mBeanConfiguration = mBeanConfiguration();
//            MBeanServlet mbeanServlet = new MBeanServlet();
//            registrationBean.setServlet(mbeanServlet);
//            registrationBean.setName(MBEAN_SERVLET_NAME);
//            registrationBean.addUrlMappings(mBeanConfiguration.getMbeanUrlPrefix() + "/*");
//            return registrationBean;
//        }
//    }


//    @Bean
//    WebMvcConfigurerAdapter mBeansWebMvcConfigurer() {
//        WebMvcConfigurerAdapter webMvcConfigurer = new WebMvcConfigurerAdapter() {
//            @Override
//            public void addResourceHandlers(ResourceHandlerRegistry pRegistry) {
//                pRegistry.addResourceHandler(MBeanController.CONTEXT_URL_PREFIX + "/static/**")
//                        .addResourceLocations(Utils.buildClassPath(MBeanController.class, "view/"));
//            }
//        };
//        return webMvcConfigurer;
//    }

    @Bean
    @Lazy
    @Primary
    @ConfigurationProperties("mbean.bean-vo-builer.default")
    GenericBeanVOBuilder defaultBeanVOBuilder() {
        return new GenericBeanVOBuilder();
    }

    @Bean
    @Lazy
    GenericBeanVOBuilder beanFactoryBeanVOBuilder() {
        return new GenericBeanVOBuilder() {
            @Override
            public String[] getIds() {
                return context.getBeanNamesForType(DefaultListableBeanFactory.class);
            }
            @Override
            protected Object populatePropertyValue(String pBeanName, PropertyDescriptor pPropertyDescriptor, ConfigurableApplicationContext pContext) {
                return super.populatePropertyValue(pBeanName, pPropertyDescriptor, pContext);
            }

            @Override
            protected Object populatePropertyValue(String pBeanName, Field pField, ConfigurableApplicationContext pContext) {
                return super.populatePropertyValue(pBeanName, pField, pContext);
            }

            @Override
            protected boolean isIgnoreProperty(String pBeanName, PropertyDescriptor pPropDesc, List<PropertyInfo> pProperties) {
                return "singletonMutex".equals(pPropDesc.getName())
                        || super.isIgnoreProperty(pBeanName, pPropDesc, pProperties);
            }

            @Override
            protected boolean isIgnoreField(String pBeanName, Field pField, List<PropertyInfo> pProperties) {
                return "frozenBeanDefinitionNames".equals(pField.getName())
                        || "filteredPropertyDescriptorsCache".equals(pField.getName())
                        || super.isIgnoreField(pBeanName, pField, pProperties);
            }
        };
    }

    @Bean
    @Lazy
    GenericBeanVOBuilder environmentBeanVOBuilder() {
        return new GenericBeanVOBuilder() {
            @Override
            public String[] getIds() {
                return context.getBeanNamesForType(Environment.class);
            }
        };
    }

    @Bean
    @Lazy
    GenericBeanVOBuilder genericConversionServiceBeanVOBuilder() {
        return new GenericBeanVOBuilder() {
            @Override
            public String[] getIds() {
                return context.getBeanNamesForType(GenericConversionService.class);
            }

            @Override
            protected Object populatePropertyValue(String pBeanName, Field pField, ConfigurableApplicationContext pContext) {
                if ("converters".equals(pField.getName())) {
                    final Object beanInstance = beansService.getBeanInstance(pBeanName, pContext);
                    final Object propInstance = Utils.getValue(pField, beanInstance);
                    String[] lines = propInstance.toString().replace("ConversionService converters =\n", "").replace("\t", "").split("\n");
                    List<ValueWrapper.BeanRef> beanRefs = beansService.detectBeanRefs(lines, 150);
//                    for (ValueWrapper.BeanRef ref : beanRefs) {
//                        ref.setToString(ref.getToString().replace(",", ",\n"));
//                    }
                    return new ValueWrapper().setValue(beanRefs);
                }
                return super.populatePropertyValue(pBeanName, pField, pContext);
            }
        };
    }



    @Bean
    @Lazy
    GenericBeanVOBuilder abstractAutoProxyCreatorBeanVOBuilder() {
        return new GenericBeanVOBuilder() {
            @Override
            public String[] getIds() {
                return context.getBeanNamesForType(AbstractAutoProxyCreator.class);
            }

            @Override
            @SuppressWarnings("unchecked")
            protected Object populatePropertyValue(String pBeanName, Field pField, ConfigurableApplicationContext pContext) {
                if ("advisedBeans".equals(pField.getName())) {
                    final Object beanInstance = beansService.getBeanInstance(pBeanName, pContext);
                    final Map<String, Boolean> propInstance = (Map<String, Boolean>) Utils.getValue(pField, beanInstance);
                    Map<String, Boolean> trueMap = new TreeMap<>();
                    Map<String, Boolean> falseMap = new TreeMap<>();
                    for (Map.Entry<String, Boolean> entry : propInstance.entrySet()) {
                        if (entry.getValue()) trueMap.put(entry.getKey(), entry.getValue());
                        else falseMap.put(entry.getKey(), entry.getValue());
                    }
                    trueMap.putAll(falseMap);
                    return populateValueWrapper(trueMap);
                }
                return super.populatePropertyValue(pBeanName, pField, pContext);
            }
        };
    }


    @Bean
    @Lazy
    GenericBeanVOBuilder configurationBeanVOBuilder() {
        return new GenericBeanVOBuilder() {
            @Override
            public String[] getIds() {
                return context.getBeanNamesForAnnotation(Configuration.class);
            }

//            @Override
//            public Object buildPropertiesView(String pBeanName, String pContextId) {
//                return null;
//            }

            @Override
            protected Method[] findMethods(String pBeanName, ConfigurableApplicationContext pContext) {
                Class<?> beanClass = beansService.getBeanClass(pContext, pBeanName, true);
                if (beanClass == null) {
                    log.warn("findMethods(): not retrieve class for bean: {0}", pBeanName);
                    return null;
                }
                return ReflectionUtils.getUniqueDeclaredMethods(beanClass);
            }
        };
    }

    @Bean
    @Lazy
    @Primary
        // @ConfigurationProperties(prefix = CONFIG_PREFIX_ACTUATOR_ADMIN_BEANS +
        // ".propertyview.builder")
    GenericPropertyVOBuilder defaultPropertyVOBuilder() {
        return new GenericPropertyVOBuilder() {
        };
    }

    public static final String BEAN_NAME_BEANFACTORY        = "beanFactory";
    public static final String BEAN_NAME_PROPERTYSOURCES    = "propertySources";

    @EventListener(ApplicationReadyEvent.class)
    private void postApplicationReady(ApplicationReadyEvent event) {
        registerSpringCommonBean(event);
    }



    private void registerSpringCommonBean(ApplicationReadyEvent event) {
        ConfigurableApplicationContext context = (ConfigurableApplicationContext) event.getApplicationContext();
        Environment environment = context.getEnvironment();
        if (environment.getProperty("spring.registerApplication", boolean.class, true)) {
            event.getApplicationContext().getBeanFactory()
                .registerSingleton("springApplication", event.getSpringApplication());
        }
        if (environment.getProperty("spring.registerBeanFactory", boolean.class, true)) {
            context.getBeanFactory().registerSingleton(BEAN_NAME_BEANFACTORY, context.getBeanFactory());
        }
        if (environment.getProperty("spring.registerPropertySources", boolean.class, true)) {
            context.getBeanFactory().registerSingleton(BEAN_NAME_PROPERTYSOURCES, context.getEnvironment().getPropertySources());
        }
    }
}
